﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Diagnostics;

using CVM;

namespace CVML
{
    public abstract class Term
    {
        public virtual IEnumerable<Term> Children { get { yield break; } }
    }

    public class Op : Term
    {
        public OpCode Code;
        public byte[] Args = new byte[0];

        static Dictionary<string, OpCode> Codes = new Dictionary<string, OpCode>();

        /// <summary>
        /// Static initializer
        /// </summary>
        static Op()
        {
            foreach (OpCode code in Enum.GetValues(typeof(OpCode)))
                Codes.Add(OpCodeShortString(code), code);
        }

        public Op(OpCode code, params Byte[] args)
        {
            Code = code;
            Args = args;
        }

        public static string OpCodeLongStringToShortString(string s)
        {
            Debug.Assert(s.StartsWith("Op_"));
            return s.Substring(3);
        }

        public static string OpCodeLongString(OpCode code)
        {
            return Enum.GetName(typeof(OpCode), code);
        }

        public static string OpCodeShortString(OpCode code)
        {
            return OpCodeLongStringToShortString(OpCodeLongString(code));
        }

        public static int NumArgs(OpCode code)
        {
            return CVM.Op.GetNumBytes(code) - 1;
        }

        public Op(IList<byte> bytes, ref int index)
        {
            Code = (OpCode)bytes[index++];
            var n = NumArgs(Code);
            Args = new byte[n];
            for (int i = 0; i < n; ++i)
                Args[i] = bytes[index++];
        }

        public Op(BinaryReader br)
        {
            Code = (OpCode)br.ReadByte();
            var n = NumArgs(Code);
            Args = new byte[n];
            for (int i = 0; i < n; ++i)
                Args[i] = br.ReadByte();
        }    

        public Op(string s)
        {
            if (s.IndexOf('%') < 0) {
                Code = Codes[s];
                return;
            }

            var codeString = s.Substring(0, s.IndexOf('%'));
            Code = Codes[codeString];
            var argString = s.Substring(s.IndexOf('%') + 1);
            var argStrings = argString.Split('%');
            var args = new List<byte>();
            foreach (var arg in argStrings)
                args.Add(Byte.Parse(arg));
            Args = args.ToArray();
        }

        public static bool IsOpCode(string s)
        {
            if (s.Contains('%'))
                s = s.Substring(0, s.IndexOf('%'));
            return Codes.ContainsKey(s);
        }

        public string LongName { get { return OpCodeLongString(Code); } }

        public string ShortName { get { return OpCodeShortString(Code); } }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append(ShortName);
            
            if (Args.Length > 0)
            {
                sb.Append('(');
                for (int i=0; i < Args.Length; ++i)
                {
                    if (i > 0) sb.Append(',');
                    sb.Append(Args[i].ToString());
                }
                sb.Append(')');
            }
            return sb.ToString();
        }

        public bool IsTerminator
        {
            get { return Code == OpCode.Op_ret; } 
        }
    }
    
    public class Constant : Term
    {
    };

    public class KInt : Constant
    {
        public KInt(int n) { this.n = n; }
        public int n;
        public override string ToString() { return n.ToString(); }
    }

    public class KFloat : Constant
    {
        public KFloat() : this(0) { }
        public KFloat(float f) { this.f = f; }
        public float f;
        public override string ToString() { return f.ToString(); }
    }    

    public class KList : Constant
    {
        public List<Constant> Data = new List<Constant>();
        public override IEnumerable<Term> Children { get { return Data.Cast<Term>(); } }
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append('(');
            for (int i = 0; i < Data.Count; ++i)
            {
                if (i > 0) sb.Append(' ');
                sb.Append(Data[i].ToString());
            }
            sb.Append(')');
            return sb.ToString();
        }
    }

    public class KTable : Constant
    {
        public List<Constant> Keys = new List<Constant>();
        public List<Constant> Values = new List<Constant>();
        public override IEnumerable<Term> Children { 
            get { 
                foreach (var k in Keys) yield return k; 
                foreach (var v in Values) yield return v; 
            } 
        }
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append('(');
            for (int i = 0; i < Keys.Count; ++i)
            {
                if (i > 0) sb.Append(' ');
                sb.Append(Keys[i].ToString());
                sb.Append(":");
                sb.Append(Values[i].ToString());
            }
            sb.Append(')');
            return sb.ToString();
        }
    }

    public class KFxn : Constant
    {
        public List<Term> Terms = new List<Term>();
        public override IEnumerable<Term> Children { get { return Terms; } }
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append('[');
            int i = 0;
            foreach (var t in Terms)
            {
                if (i++ > 0) sb.Append(' ');
                sb.Append(t.ToString());
            }
            sb.Append(']');
            return sb.ToString();
        }

    }
}
